package sim1011.poltergeist;

import jade.core.Agent;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.wrapper.ControllerException;

import java.util.concurrent.Semaphore;

import sim1011.behaviours.ResourceManagerBehaviour;
import sim1011.components.Component;
import sim1011.starter.EctoMobileStarter;
import sim1011.starter.Logger;

public class ResourceManager extends Agent {
	private static final float BATTERY_FOR_MEMORY_REQ = 0.002f;
	private static final float BATTERY_FOR_OPERATION_CPU = 0.01f;
	private static final long serialVersionUID = 4466364229440636139L;
	private float batteryLevel = -1.0f;
	private int costCPU; // costo cpu per unità
	private int costMemory; // costo memoria per unità
	private Semaphore cpuSemaphore;
	private float currentCost = 0.0f;
	private Semaphore memorySemaphore;
	private int NOMINAL_CPU; // cpu nominale
	private int NOMINAL_MEMORY; // memoria nominale
	private float currentThroughput = 0;

	public float consumeBatteryOnExecution(int requiredCPU, int requiredMemory) {
		float consumed = BATTERY_FOR_OPERATION_CPU * requiredCPU
				+ BATTERY_FOR_MEMORY_REQ * requiredMemory;
		try {
			if (getContainerController().getContainerName().equals("Node.M"))
				Logger.log("[RESOURCEMANAGER "
						+ getContainerController().getContainerName()
						+ "] consumo " + consumed + "% di batteria");
		} catch (ControllerException e) {
			e.printStackTrace();
		}
		return consumed;
	}

	public void consumeResource(int consumedCPU, int consumedMemory) {
		try {
			Logger.log("[RESOURCEMANAGER] acquisizione risorse: "
					+ getContainerController().getContainerName());
			int cpuAv = cpuSemaphore.availablePermits();
			cpuSemaphore.acquire(consumedCPU);
			memorySemaphore.acquire(consumedMemory);
			currentCost += this.costExecutionOnNode(consumedCPU, consumedMemory);
			//currentThroughput += consumedCPU;
			currentThroughput += this.NOMINAL_CPU/consumedCPU;
			Logger.log("[RESOURCEMANAGER] acquisite risorse: "
					+ getContainerController().getContainerName());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ControllerException e) {
			e.printStackTrace();
		}

	}

	private Float costExecutionOnNode(int reqCPU, int reqMEM) {
		// il costo di esecuzione è la quantità di cpu e memoria richiesta
		// moltiplicata per il costo unitario di cpu e memoria del nodo che si
		// considera
		return reqCPU * costCPU * EctoMobileStarter.CPU_WEIGHT + reqMEM
				* costMemory * EctoMobileStarter.MEMORY_WEIGHT;
	}

	public float getBatteryLevel() {
		return batteryLevel;
	}

	public int getCostCPU() {
		return costCPU;
	}

	public int getCostMemory() {
		return costMemory;
	}

	public float getCurrentCost() {
		return currentCost;
	}

	public int getFreeCPU() {
		return cpuSemaphore.availablePermits();
	}

	public int getFreeMemory() {
		return memorySemaphore.availablePermits();
	}

	public int getNOMINAL_CPU() {
		return NOMINAL_CPU;
	}

	public int getNOMINAL_MEMORY() {
		return NOMINAL_MEMORY;
	}

	public void releaseResource(int releasedCPU, int releasedMemory) {
		cpuSemaphore.release(releasedCPU);
		memorySemaphore.release(releasedMemory);
		try {
			Logger.log("[RESOURCEMANAGER] rilascio risorse: "
					+ getContainerController().getContainerName());
		} catch (ControllerException e) {
			e.printStackTrace();
		}
	}

	public void setBatteryLevel(float batteryLevel) {
		this.batteryLevel = batteryLevel;
	}

	public void setCostCPU(int costCPU) {
		this.costCPU = costCPU;
	}

	public void setCostMemory(int costMemory) {
		this.costMemory = costMemory;
	}

	public void setNOMINAL_CPU(int nOMINAL_CPU) {
		NOMINAL_CPU = nOMINAL_CPU;
	}

	public void setNOMINAL_MEMORY(int nOMINAL_MEMORY) {
		NOMINAL_MEMORY = nOMINAL_MEMORY;
	}

	public void setup() {
		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(getAID());
		ServiceDescription sd = new ServiceDescription();
		sd.setType("ecto-resource");
		sd.setName("ecto-resource." + this.getLocalName());
		Logger.log("[RESOURCEMANAGER] registering ecto-resource."
				+ this.getLocalName());
		dfd.addServices(sd);
		try {
			DFService.register(this, dfd);
		} catch (FIPAException fe) {
			fe.printStackTrace();
		}
		addBehaviour(new ResourceManagerBehaviour());
		cpuSemaphore = new Semaphore(NOMINAL_CPU, false);
		memorySemaphore = new Semaphore(NOMINAL_MEMORY, false);
	}

	public void takeDown() {
		try {
			DFService.deregister(this);
		} catch (FIPAException fe) {
			fe.printStackTrace();
		}
	}
	
	public void resetThroughupt(){
		currentThroughput = 0.0f;
	}
	
	public float getCurrentThroughput(){
		return currentThroughput;
	}

	public float consumeBatteryOnMigration(int requiredCPU, int requiredMemory, int QoS) {
		float newQoS = 1 + ((QoS-1)/11);
		return ((EctoMobileStarter.BATTERY_FOR_TRANSFER/(newQoS)) * (Component
				.size(requiredCPU, requiredMemory*2) + EctoMobileStarter.MIGRATION_OVERHEAD));
	}
}
